﻿using System;
using System.ServiceModel.Dispatcher;
using EnterpriseLibraryExtensions.Events.Aggregation;

namespace EnterpriseLibraryExtensions.Events.ServiceModel
{
    /// <summary>
    /// The class is injected into the WCF pipeline and makes sure that any service call is encapsulated in
    /// an event aggregation scope which auto completes if the service call does not fail.
    /// </summary>
    internal class EventAggregationScopeInspector : IParameterInspector, IDisposable
    {
        /// <summary>
        /// The options to pass to the event aggregation scope. This is an instance of the type
        /// declared in the constructor.
        /// </summary>
        private readonly IScopeOptions _scopeOptions;

        /// <summary>
        /// The event aggregation scope instance for the service call.
        /// </summary>
        private EventAggregationScope _eventAggregationScope;

        /// <summary>
        /// Initializes a new instance of the <see cref="EventAggregationScopeInspector"/> class.
        /// </summary>
        /// <param name="scopeOptions">The scope options. Value cannot be null.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if scopeOptions is null.</exception>
        public EventAggregationScopeInspector(IScopeOptions scopeOptions)
        {
            if (scopeOptions == null)
            {
                throw new ArgumentNullException("scopeOptions");
            }

            _scopeOptions = scopeOptions;
        }

        /// <summary>
        /// Called before client calls are sent and after service responses are returned.
        /// </summary>
        /// <param name="operationName">The name of the operation.</param>
        /// <param name="inputs">The objects passed to the method by the client.</param>
        /// <returns>
        /// The correlation state that is returned as the correlationState parameter in <see cref="M:System.ServiceModel.Dispatcher.IParameterInspector.AfterCall(System.String,System.Object[],System.Object,System.Object)"/>. Return null if you do not intend to use correlation state.
        /// </returns>
        public object BeforeCall(string operationName, object[] inputs)
        {
            _eventAggregationScope = new EventAggregationScope(EventAggregationMode.RequireNew);
            _eventAggregationScope.Options = _scopeOptions;

            return null;
        }

        /// <summary>
        /// Called after client calls are returned and before service responses are sent.
        /// </summary>
        /// <param name="operationName">The name of the invoked operation.</param>
        /// <param name="outputs">Any output objects.</param>
        /// <param name="returnValue">The return value of the operation.</param>
        /// <param name="correlationState">Any correlation state returned from the <see cref="M:System.ServiceModel.Dispatcher.IParameterInspector.BeforeCall(System.String,System.Object[])"/> method, or null.</param>
        public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
        {
            _eventAggregationScope.Complete();
            _eventAggregationScope.Dispose();
            _eventAggregationScope = null;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (_eventAggregationScope != null)
            {
                _eventAggregationScope.Dispose();
                _eventAggregationScope = null;
            }
        }
    }
}